﻿/*
 * @Autor: PK
 * @Date: 2020-12-28 09:15:40
 * @Description: 路网的抽象类，在Network基础上设置了默认的一些功能，可以继承该类实现自己的路网，减少代码
 */

using System;
using System.Collections.Generic;
using System.Diagnostics;
using NLog;
using O2DESNet;
using ShkSimulation.core.dao.input.resource;
using ShkSimulation.core.dao.output.result;
using ShkSimulation.core.dao.statistic;
using ShkSimulation.core.exceptions;
using ShkSimulation.core.factory;
using ShkSimulation.core.template.agency;
using ShkSimulation.core.template.customer;
using ShkSimulation.core.template.model;
using ShkSimulation.core.util;

namespace ShkSimulation.core.template {
    
    public delegate List<AgencyFactory> CreateFactoriesFunc(NetworkInputData inputData);

    [Memento]
    public abstract class AbstractNetwork: Sandbox, Network {

        protected static readonly Logger logger = LogManager.GetLogger(nameof(AbstractNetwork));
        
        public int UniqueId { get; private set; }
        protected bool initialized;
        public DemandManager DemandManager { get; private set; }
        public NetworkInputData NetworkInput { get; private set; }
        public NetworkResultCollector ResultCollector { get; private set; }
        public Dictionary<int, Agency> Agencies { get; protected set; } = new Dictionary<int, Agency>();
        
        protected List<AgencyFactory> agencyFactories;
        protected Dictionary<string, Dictionary<int, Agency>> agencies = new Dictionary<string, Dictionary<int, Agency>>();
        
        public DateTime NetworkCurrentTime => ClockTime;
        public DateTime NetworkStartTime { get; private set; }
        
        private CreateFactoriesFunc CreateFactoriesAction { get; set; }

        protected AbstractNetwork(int id,
            NetworkInputData inputData, 
            NetworkResultCollector resultCollector,
            DemandManager demandManager, 
            CreateFactoriesFunc createFactoriesFunc, 
            int seed) : base(seed, id.ToString()) {
            
            this.UniqueId = id;
            this.NetworkInput = inputData ?? throw new SimulationInitException($"创建参数不足，路网创建失败，参数:{nameof(inputData)}");
            this.ResultCollector = resultCollector ?? throw new SimulationInitException($"创建参数不足，路网创建失败，参数:{nameof(resultCollector)}");
            this.DemandManager = demandManager ?? throw new SimulationInitException($"创建参数不足，路网创建失败，参数:{nameof(demandManager)}");
            this.CreateFactoriesAction = createFactoriesFunc ?? throw new SimulationInitException($"创建参数不足，路网创建失败，参数:{nameof(createFactoriesFunc)}");
            AddChild(this.DemandManager);
        }

        public Result GetCurrentResult(string key = null) {
            if (key == null) {
                key = $"UniqueId:{UniqueId}-{ClockTime.ToString("yyyy-MM-dd HH-mm-ss")}";
            }
            return this.ResultCollector.GetCurrentResult(key);
        }
        
        public event Action<NetworkRunInfo> BeforeInit;
        public event Action<NetworkRunInfo> AfterInitFailed;
        public event Action<NetworkRunInfo> AfterInitSuccess;
        public event Action<NetworkRunInfo> BeforeSimulationRun;
        public event Action<NetworkRunInfo> OnSimulationRunning;
        public event Action<NetworkRunInfo> AfterRunCompleted;
        public event Action<NetworkRunInfo> AfterRunCanceled;
                
        #region Actions
        public event Action<Network> WhenInitStart;
        public event Action<Network> BeforeFactoryCreate;
        public event Action<NetworkInputData, List<AgencyFactory>> AfterFactoryCreated;
        public event Action<NetworkInputData> BeforeAgenciesCreate;
        public event Action<NetworkInputData, Dictionary<string, List<Agency>>> AfterAgenciesCreated;
        public event Action<NetworkInputData> BeforeDemandManagerInitialize;
        public event Action<NetworkInputData,  DemandManager> AfterDemandManagerInitialized;
        public event Action<NetworkInputData, NetworkResultCollector> BeforeCreateStatisticTools;
        public event Action<NetworkInputData, NetworkResultCollector, DemandManager, IReadOnlyDictionary<int, Agency>> AfterRegisterStatistic;
        #endregion

        public bool IsRunning { get; set; }
        
        public bool Cancel { get; set; }

        public bool RunWithTerminate(DateTime terminate, TimeSpan observeSpan) {
            if (IsRunning) {
                throw new ErrorParamException($"仿真已经在运行了");
            }
            IsRunning = true;
            BeforeSimulationRun?.Invoke(NetworkRunInfo.CreateByMessage(this, $"开始仿真, UniqueId:{UniqueId}"));
            if (observeSpan <= TimeSpan.Zero) {
                BeforeSimulationRun?.Invoke(NetworkRunInfo.CreateByMessage(this, $"仿真观察间隔不符合要求设置为最大值：{observeSpan}"));
                observeSpan = TimeSpan.MaxValue;
            }
            long count = (terminate - ClockTime).Ticks / observeSpan.Ticks;
            long totalCount = count;
            long rest = (terminate - ClockTime).Ticks % observeSpan.Ticks;
            TimeSpan lastRunTime = TimeSpan.FromTicks(rest);
            bool canBeContinued = false;
            bool isCanceled = false;
            while (count > 0) {
                if (Cancel) {
                    isCanceled = true;
                    Cancel = false;
                    break;
                }
                canBeContinued = Run(observeSpan);
                TimeSpan runDays = (totalCount - count) * observeSpan;
                TimeSpan restDays = terminate - ClockTime;
                OnSimulationRunning?.Invoke(NetworkRunInfo.CreateByMessage(this, $"仿真进行了：{runDays.Days}天{runDays.Hours}时，" +
                                                                         $"剩余：{restDays.Days}天{restDays.Hours}时"));
                if (!canBeContinued) {
                    AfterRunCompleted?.Invoke(NetworkRunInfo.CreateByMessage(this, $"仿真由于没有事件提前结束了：{ClockTime}, 剩余未完成仿真时长：{terminate - ClockTime}"));
                    IsRunning = false;
                    return false;
                }
                count--;
            }
            if (!isCanceled && lastRunTime > TimeSpan.Zero) {
                canBeContinued = Run(lastRunTime);
            }
            AfterRunCompleted?.Invoke(NetworkRunInfo.CreateByMessage(this, $"仿真结束：{ClockTime}"));
            IsRunning = false;
            return canBeContinued;
        }
        
        public bool RunWithTerminate(TimeSpan runTime, TimeSpan observeSpan) {
            return RunWithTerminate(ClockTime + runTime, observeSpan);
        }

        public bool ContinousRun(TimeSpan totalRunTime, TimeSpan observeSpan) {
            if (IsRunning) {
                throw new ErrorParamException($"仿真已经在运行了");
            }
            if (totalRunTime <= TimeSpan.Zero || observeSpan <= TimeSpan.Zero) {
                throw new ErrorParamException($"可别呀，时间得大于零啊，总时长{totalRunTime}，观察间隔{observeSpan}");
            }
            IsRunning = true;
            long observe = observeSpan.Ticks;
            long totalTicks = totalRunTime.Ticks;
            Stopwatch stopwatch = Stopwatch.StartNew();
            BeforeSimulationRun?.Invoke(NetworkRunInfo.CreateByMessage(this, $"开始仿真, UniqueId: {UniqueId}"));
            long count = 0;
            while (stopwatch.ElapsedTicks < totalTicks) {
                if (Cancel) {
                    Cancel = false;
                    IsRunning = false;
                    AfterRunCompleted?.Invoke(NetworkRunInfo.CreateByMessage(this, $"仿真结束：{ClockTime}"));
                    return false;
                }
                bool canBeContinued = Run();
                long elapsed = stopwatch.ElapsedTicks;
                if (elapsed / observe > count) {
                    count = elapsed / observe;
                    OnSimulationRunning?.Invoke(NetworkRunInfo.CreateByMessage(this, $"仿真观察：{ClockTime}"));
                }
                if (!canBeContinued) {                    
                    IsRunning = false;
                    AfterRunCompleted?.Invoke(NetworkRunInfo.CreateByMessage(this, $"仿真由于没有事件提前结束了：{ClockTime}"));
                    return false;
                }
            }
            stopwatch.Stop();
            AfterRunCompleted?.Invoke(NetworkRunInfo.CreateByMessage(this, $"仿真结束：{ClockTime}，运行时长：{stopwatch.Elapsed}"));
            IsRunning = false;
            return true;
        }

        public NetworkRunInfo Init() {
            return Init(DateTime.MinValue);
        }

        public NetworkRunInfo Init(DateTime startTime) {
            if (initialized) {
                return NetworkRunInfo.CreateByException(this, new SimulationInitException("请不要重复初始化"));
            }
            Stopwatch initalTimer = new Stopwatch();
            initalTimer.Start();
            BeforeInit?.Invoke(NetworkRunInfo.CreateByMessage(this, "初始化前"));
            NetworkStartTime = startTime;
            try {
                // 时间
                Run(startTime);
                logger.Info($"开始时间设置到：{startTime}");
                // 开始
                WhenInitStart?.Invoke(this);
                BeforeFactoryCreate?.Invoke(this);
                // 创建服务机构工厂
                agencyFactories = CreateFactoriesAction(NetworkInput);
                // 工厂创建后
                AfterFactoryCreated?.Invoke(NetworkInput, agencyFactories);
                BeforeAgenciesCreate?.Invoke(NetworkInput);
                // 服务机构创建
                Dictionary<string, List<Agency>> agencyDictionary = new Dictionary<string, List<Agency>>();
                Dictionary<int, Agency> allAgencies = new Dictionary<int, Agency>();
                NetworkInput.ClassifiedAgencies = agencyDictionary;
                NetworkInput.AllAgencies = allAgencies;
                // 需要按照顺序
                foreach (var agencyFactory in agencyFactories) {
                    if (!NetworkInput.AllResources.ContainsKey(agencyFactory.ResourceKey)) {
                        logger.Warn("服务机构工厂:{Key} 没有对应输入文件", agencyFactory.ResourceKey);
                    }
                    Dictionary<int, Agency> createdAgencies = CreateAgenciesAsChildren(NetworkInput.AllResources.GetValueOrDefault(agencyFactory.ResourceKey), agencyFactory);
                    agencyDictionary[agencyFactory.ResourceKey] = new List<Agency>(createdAgencies.Values);
                    foreach (var (uniqueId, agency) in createdAgencies) {
                        if (allAgencies.ContainsKey(uniqueId)) {
                            throw new SimulationInitException("仿真初始化失败，服务机构uniqueId重复：" + uniqueId);
                        }
                        allAgencies[uniqueId] = agency;
                    }
                }
                // 对服务机构跟踪
                AfterAgenciesCreated?.Invoke(NetworkInput, agencyDictionary);
                // 设置需求管理器
                BeforeDemandManagerInitialize?.Invoke(NetworkInput);
                DemandManagerInitialize(NetworkInput);
                AfterDemandManagerInitialized?.Invoke(NetworkInput, DemandManager);
                // 注册统计处理器
                BeforeCreateStatisticTools?.Invoke(NetworkInput, ResultCollector);
                ResultCollector.InitStatistics(this);
                AfterRegisterStatistic?.Invoke(NetworkInput, ResultCollector, DemandManager, Agencies.AsReadOnly());
                // 需求管理器开始工作
                Schedule(DemandManager.Start);
            } catch (Exception e) {
                logger.Error(e.StackTrace);
                logger.Error($"启动时发生错误:{e.Message}");
                NetworkRunInfo info = NetworkRunInfo.CreateByException(this, e);
                AfterInitFailed?.Invoke(info);
                return info;
            }
            initialized = true;
            initalTimer.Stop();
            string message = $"路网初始化完成，时间: {initalTimer.ElapsedMilliseconds} ms";
            logger.Info(message);
            NetworkRunInfo successInfo = NetworkRunInfo.CreateByMessage(this, message);
            AfterInitSuccess?.Invoke(successInfo);
            return successInfo;
        }
        
        private void DemandManagerInitialize(NetworkInputData networkInput) {
            networkInput.CustomerTemplate = DemandManager.InitializeCustomerTemplate(networkInput);
            DemandManager.Templates = new List<CustomerTemplate>(networkInput.CustomerTemplate);
        }
        
        /**
         * 使用工厂生产服务机构,必须确保stations/road的Id、Name唯一
         */
        private Dictionary<int, Agency> CreateAgenciesAsChildren(Resource resource, AgencyFactory agencyFactory)  {
            Dictionary<int, Agency> result = agencyFactory.Create(resource);
            foreach (var each in result.Values) {
                if (each.UniqueId == this.UniqueId) {
                    throw new ErrorParamException($"Agency.Id 不能和路网Id相同：{UniqueId}");
                }
                if (each.Name == this.UniqueId.ToString()) {
                    throw new ErrorParamException($"Agency.Name 不能和路网 UniqueId 相同：{UniqueId}");
                }
                if (this.Agencies.ContainsKey(each.UniqueId)) {
                    throw new ErrorParamException("Agency.Id 重复");
                }
                this.Agencies.Add(each.UniqueId, each);
                this.AddChild(each);
            }
            return result;
        }

        /**
        * @description: 默认使用反射对相应属性或字段赋值，如有其他需要请重写，因此最好保证该方法只在初始化时使用，
        * @param 需要通过反射设置的public属性或字段，非常不建议将字段设置为public
        */
        public virtual void SetPropertyAndField(Dictionary<string, object> attributes) {
            RelectionUtil.SetPropertyAndField(this, attributes);
        }
        
        /**
        * @description: DataReport
        */
        public event Action<DataReporter> ReportToCollection = _ => { };

    }
}
